package com.coolxiaoyao.common.handler;

import com.coolxiaoyao.common.annotation.*;
import com.coolxiaoyao.common.constant.Constants;
import com.coolxiaoyao.common.exception.MappingException;
import com.coolxiaoyao.common.http.HttpMethod;
import com.coolxiaoyao.common.mapping.UrlMapping;
import com.coolxiaoyao.common.util.ClassUtil;
import com.coolxiaoyao.common.util.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Kerry on 18/09/20
 */

public class AnnotationHandler {


    private Map<String, Map<HttpMethod, UrlMapping>> urlMapping;

    public static Map<String, Map<HttpMethod, UrlMapping>> scannerByClass(Class<?>... primaryClass) throws Exception {
        return new AnnotationHandler().scannerUrlMapping(primaryClass);
    }

    public static Map<String, Map<HttpMethod, UrlMapping>> scannerByPackage(String... primaryClass) throws Exception {
        return new AnnotationHandler().scannerUrlMapping(primaryClass);
    }

    private AnnotationHandler() {
        urlMapping = new ConcurrentHashMap<>();
    }


    private Map<String, Map<HttpMethod, UrlMapping>> scannerUrlMapping(Class<?>... primaryClass) throws Exception {

        for (Class<?> clazz : primaryClass) {
            if (clazz.getAnnotation(RestController.class) != null) {
                initRestControllerClass(clazz);
            }
        }
        return urlMapping;
    }

    private Map<String, Map<HttpMethod, UrlMapping>> scannerUrlMapping(String... primaryClass) throws Exception {
        Set<Class<?>> classSet = ClassUtil.scannerClassByAnnotation(RestController.class, primaryClass);
        for (Class<?> clazz : classSet) {
            initRestControllerClass(clazz);
        }
        return urlMapping;
    }


    @SuppressWarnings("ConstantConditions")
    private void initRestControllerClass(Class<?> clazz) throws InstantiationException, IllegalAccessException, MappingException {
        RestController annotation = clazz.getAnnotation(RestController.class);
        assert annotation != null;

        // @RestController("...") 这里面的值
        String prefix = annotation.value();
        // Create Instance
        Object controller = clazz.newInstance();
        // get All Instance Methods
        Method[] declaredMethods = clazz.getDeclaredMethods();

        for (Method method : declaredMethods) {
            // getMethod Params Annotations List, One Field can has one or more annotation
            Annotation[][] parameterAnnotations = method.getParameterAnnotations();
            // getMethod Params Class type
            Class<?>[] parameterTypes = method.getParameterTypes();

            //  一个方法上可能有多个注解, 这些URL可能不同,可能相同
            // @***Mapping
            // List<String> urls = this.mappingAnnotation(prefix, clazz, controller, method);
            Map<String, HttpMethod> urlMethod = new LinkedHashMap<>(8);
            GetMapping getMapping = method.getDeclaredAnnotation(GetMapping.class);
            if (getMapping != null) {
                String url = checkUrlValid(prefix, getMapping.value());
                urlMethod.put(url, HttpMethod.GET);
            }
            PostMapping postMapping = method.getDeclaredAnnotation(PostMapping.class);
            if (postMapping != null) {
                String url = checkUrlValid(prefix, postMapping.value());
                urlMethod.put(url, HttpMethod.POST);
            }
            PutMapping putMapping = method.getDeclaredAnnotation(PutMapping.class);
            if (putMapping != null) {
                String url = checkUrlValid(prefix, putMapping.value());
                urlMethod.put(url, HttpMethod.PUT);
            }
            DeleteMapping deleteMapping = method.getDeclaredAnnotation(DeleteMapping.class);
            if (deleteMapping != null) {
                String url = checkUrlValid(prefix, deleteMapping.value());
                urlMethod.put(url, HttpMethod.DELETE);
            }


            for (int i = 0; i < parameterAnnotations.length; i++) {
                if (parameterAnnotations[i].length > 0) {
                    //TODO 三选一.不能三个注解同时用在同一个字段上.
                    boolean isPathVariable = false;
                    boolean isRequestBody = false;
                    boolean isRequestParam = false;

                    for (Annotation tempAnnotation : parameterAnnotations[i]) {
                        if (tempAnnotation instanceof RequestBody) {
                            isRequestBody = true;
                            boolean b = ClassUtil.supportRequestBodyClassType(parameterTypes[i]);
                            if (!b) {
                                throw new MappingException("Annotation. Class: " + RequestBody.class + ", UnSupport ClassType." + parameterTypes[i] + ",in " + method);
                            }
                        }
                        if (tempAnnotation instanceof RequestParam) {
                            isRequestParam = true;
                            boolean b = ClassUtil.supportRequestParamClassType(parameterTypes[i]);
                            if (!b) {
                                throw new MappingException("Annotation. Class: " + RequestParam.class + ", UnSupport ClassType." + parameterTypes[i] + ",in " + method);
                            }
                        }
                        if (tempAnnotation instanceof PathVariable) {
                            isPathVariable = true;

                            boolean isSupport = ClassUtil.supportPathVariableClassType(parameterTypes[i]);
                            if (!isSupport) {
                                throw new MappingException("Annotation. Class: " + PathVariable.class + ", UnSupport ClassType." + parameterTypes[i] + ",in " + method);
                            }


                            Map<String, HttpMethod> tempMap = new LinkedHashMap<>(urlMethod.size());
                            for (Map.Entry<String, HttpMethod> entry : urlMethod.entrySet()) {
                                String url = entry.getKey();
                                String[] split = url.split("/");
                                String keyName = "{" + ((PathVariable) tempAnnotation).value() + "}";


                                int pathVariableCount = 0;
                                // Replace {id} with .* pattern
                                for (int p = 0; p < split.length; p++) {
                                    if (keyName.equals(split[p])) {
                                        pathVariableCount++;
                                        split[p] = "(" + Constants.PATH_VARIABLE_PATTERN + ")";
                                    }
                                }
                                // only support one
                                if (pathVariableCount != 1) {
                                    throw new MappingException(method + "; url =" + url + "; " + PathVariable.class + " 'value = " + keyName);
                                }
                                tempMap.put(String.join("/", split), entry.getValue());
                            }
                            urlMethod.clear();
                            urlMethod.putAll(tempMap);
                        }
                    }
                    // check
                    if (isPathVariable && isRequestBody && isRequestParam) {
                        throw new MappingException("both Annotation coexist." + RequestBody.class + "," + PathVariable.class);
                    }
                }

            } //end method param loop

            // add to Map
            for (Map.Entry<String, HttpMethod> methodEntry : urlMethod.entrySet()) {
                String url = methodEntry.getKey();
                HttpMethod httpMethod = methodEntry.getValue();
                Map<HttpMethod, UrlMapping> temp = urlMapping.get(url);
                if (temp == null) {
                    temp = new HashMap<>(4);
                }
                UrlMapping put = temp.put(httpMethod, new UrlMapping(controller, method, parameterTypes));
                if (put != null) {
                    throw new MappingException("class: " + clazz + ",method: " + method + ",url: " + url);
                }
                urlMapping.put(url, temp);
            }
        }
    }


    private String checkUrlValid(String prefix, String value) throws MappingException {
        String url = prefix + value;
        if (StringUtils.isBlank(url)) {
            //URL 不能为空
            throw new MappingException("URL is Empty in method: ");
        }
        return url;
    }


    @Deprecated
    private List<String> mappingAnnotation(String prefix, Class<?> clazz, Object controllerInstance, Method method) throws
            MappingException {

        List<String> urls = new ArrayList<>(6);
        GetMapping getMapping = method.getDeclaredAnnotation(GetMapping.class);
        if (getMapping != null) {
            String url = checkUrlValid(prefix, getMapping.value());
            url = mappingAnnotationOne(url, HttpMethod.GET, clazz, controllerInstance, method);
            urls.add(url);
        }
        PostMapping postMapping = method.getDeclaredAnnotation(PostMapping.class);
        if (postMapping != null) {
            String url = checkUrlValid(prefix, postMapping.value());
            url = mappingAnnotationOne(url, HttpMethod.POST, clazz, controllerInstance, method);
            urls.add(url);
        }
        PutMapping putMapping = method.getDeclaredAnnotation(PutMapping.class);
        if (putMapping != null) {
            String url = checkUrlValid(prefix, putMapping.value());
            url = mappingAnnotationOne(url, HttpMethod.PUT, clazz, controllerInstance, method);
            urls.add(url);
        }
        DeleteMapping deleteMapping = method.getDeclaredAnnotation(DeleteMapping.class);
        if (deleteMapping != null) {
            String url = checkUrlValid(prefix, deleteMapping.value());
            url = mappingAnnotationOne(url, HttpMethod.DELETE, clazz, controllerInstance, method);
            urls.add(url);
        }
        return urls;
    }

    @Deprecated
    private String mappingAnnotationOne(String url, HttpMethod httpMethod, Class<?> clazz, Object
            controllerInstance, Method method) throws MappingException {

        if (StringUtils.isBlank(url)) {
            //URL 不能为空
            throw new MappingException("URL is Empty in method: " + method);
        }
        Map<HttpMethod, UrlMapping> temp = urlMapping.get(url);
        if (temp == null) {
            temp = new HashMap<>(4);
            urlMapping.put(url, temp);
        } else {
            if (temp.containsKey(httpMethod)) {
                //URL 相同了
                throw new MappingException("class: " + clazz + ",method: " + method + ",url: " + url);
            }
        }
        temp.put(httpMethod, new UrlMapping(controllerInstance, method, method.getParameterTypes()));
        return url;
    }


}
